package pl.edu.agh.neurony.logic;

import java.util.ArrayList;
import java.util.List;

import pl.edu.agh.neurony.exceptions.BadNeuronInitiationException;
import pl.edu.agh.neurony.exceptions.LayerConstructionException;
import pl.edu.agh.neurony.exceptions.ProhibitedOperationException;

public class KohonenLayer extends Layer {

	public static int no_neighborhood = 0;
	public static int neighborhood_1D = 1;
	public static int neighborhood_2D = 2;
	private Integer neighborhoodSize;
	private Integer neighborhood;
	private Double conscienceFactor;
	private List<Double> conscienceValues;

	public void setNeighborhoodSize(Integer neighborhoodSize) {
		this.neighborhoodSize = neighborhoodSize;
	}

	public Integer getNeighborhood() {
		return neighborhood;
	}

	public void setNeighborhood(Integer neighborhood) {
		this.neighborhood = neighborhood;
	}

	public Double getConscienceFactor() {
		return conscienceFactor;
	}

	public void setConscienceFactor(Double conscienceFactor) {
		this.conscienceFactor = conscienceFactor;
	}

	public List<Double> getConscienceValues() {
		return conscienceValues;
	}

	public void setConscienceValues(List<Double> conscienceValues) {
		this.conscienceValues = conscienceValues;
	}

	public KohonenLayer(int layerNumber, Neuron neuron, int neighborhood,
			double learningFactor, int learningSteps, double conscienceFactor)
			throws LayerConstructionException {
		super(layerNumber, neuron);
		this.neighborhood = neighborhood;
		this.learningFactor = learningFactor;
		conscienceValues = new ArrayList<Double>(neurons.size());
		for (int i = 0; i < neurons.size(); i++) {
			conscienceValues.add(new Double(1.0));
		}
		this.conscienceFactor = conscienceFactor;
	}

	public KohonenLayer(int layerNumber, Neuron neuron)
			throws LayerConstructionException {
		super(layerNumber, neuron);
		conscienceValues = new ArrayList<Double>(neurons.size());
		for (int i = 0; i < neurons.size(); i++) {
			conscienceValues.add(new Double(1.0));
		}
	}

	@Override
	void addNeuron(Neuron neuron) throws LayerConstructionException {
		super.addNeuron(neuron);
		conscienceValues.add(1.0);
	}

	@Override
	Neuron removeLastNeuron() throws ProhibitedOperationException {
		conscienceValues.remove(conscienceValues.size() - 1);
		return super.removeLastNeuron();
	}

	public double calculateNeighborhood(Neuron n1, Neuron n2) {
		int a = 0, b = 0;
		for (int i = 0; i < neurons.size(); i++) {
			if (neurons.get(i).equals(n1)) {
				a = i;
			}
			if (neurons.get(i).equals(n2)) {
				b = i;
			}
		}
		if (a == b)
			return 0.0;
		else if (neighborhood == KohonenLayer.neighborhood_1D) {
			return Math.abs(a - b);
		} else if (neighborhood == KohonenLayer.neighborhood_2D) {
			int rowSize = -1;
			for (int i = (int) Math.sqrt(neurons.size()); i > 0; i--) {
				if (neurons.size() % i != 0 && rowSize > 0) {
					break;
				}
				if (neurons.size() % i == 0) {
					rowSize = i;
				}
			}
			int x1 = a % rowSize;
			int y1 = a / rowSize;
			int x2 = b % rowSize;
			int y2 = b / rowSize;
			return Math.abs(x2 - x1) + Math.abs(y2 - y1);

		}
		return Double.MAX_VALUE;
	}

	private void learnStep() throws BadNeuronInitiationException {
		Neuron winner = choseWinner();
		setConscienceValues(winner);
		updateWeights(winner);
	}

	private Neuron choseWinner() throws BadNeuronInitiationException {
		double minError = Double.MAX_VALUE;
		Neuron retNeuron = null;
		for (int i = 0; i < neurons.size(); i++) {
			Neuron n = neurons.get(i);
			double tmpError = n.getError();
			if (minError > tmpError
					&& conscienceValues.get(i) >= conscienceFactor) {
				minError = tmpError;
				retNeuron = n;
			}
		}
		return retNeuron;
	}

	private void setConscienceValues(Neuron winner) {
		for (int i = 0; i < conscienceValues.size(); i++) {
			double value = conscienceValues.get(i).doubleValue();
			if (neurons.get(i).equals(winner)) {
				conscienceValues.set(i, new Double(value - conscienceFactor));
			} else {
				double newValue = value + 1.0 / (conscienceValues.size() - 1);
				if (newValue > 1.0)
					newValue = 1.0;
				conscienceValues.set(i, new Double(newValue));
			}
		}

	}

	private void updateWeights(Neuron winner)
			throws BadNeuronInitiationException {
		for (Neuron n : neurons) {
			double neighborhood = calculateNeighborhood(n, winner);
			if (neighborhood <= neighborhoodSize) {
				neighborhood = 1 / (1 + neighborhood);
				List<Connection> connections = n.getInputConnections();
				for (int i = 0; i < connections.size(); i++) {
					Connection c = connections.get(i);
					double weight = c.getWeight();
					double newWeight = weight + learningFactor * neighborhood
							* (c.getInputNeuron().calculateOutput() - weight);
					c.setWeight(newWeight);
				}
			}
		}

	}

	public void learn(int step, int steps) throws BadNeuronInitiationException {
		for (int i = 1; i <= steps; i++) {
			if (Net.getInstance().getInputsValues() != null) {
				for (List<Double> input : Net.getInstance().getInputsValues()) {
					Net.getInstance().fillInputsWithData(input);
					learnStep();
				}
			} else {
				learnStep();
			}
			updateLearningFactor(i, steps);
		}
		System.out.println(this);
		if (Net.getInstance().getInputsValues() != null) {
			for (int i = 0; i < Net.getInstance().getInputsValues().size(); i++) {
				System.out.print("nr " + i + "\t");
				Net.getInstance().fillInputsWithData(Net.getInstance().getInputsValues().get(i));
				setOutputs();
				for (Neuron nn : neurons)
					System.out.print(nn.output + " ");
				System.out.println();
			}
		} else {
			setOutputs();
			for (Neuron nn : neurons)
				System.out.print(nn.output + " ");
			System.out.println();
		}
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Kohonen Layer: ");
		int i = 0;
		for (Neuron n : this.getNeurons()) {
			sb.append("Neuron " + i++ + "\n");
			for (Connection c : n.getInputConnections()) {
				sb.append(c.getWeight() + "\t");
			}
			sb.append("\n");
		}
		return sb.toString();
	}

	public void setOutputs() {
		Neuron winner;
		try {
			conscienceValues = new ArrayList<Double>(neurons.size());
			for (int i = 0; i < neurons.size(); i++) {
				conscienceValues.add(new Double(1.0));
			}
			winner = choseWinner();

			for (Neuron n : neurons) {
				if (n.equals(winner)) {
					n.output = 1.0;

				} else {
					n.output = 0.0;
				}
			}
		} catch (BadNeuronInitiationException e) {
			
			e.printStackTrace();
		}

	}

	@Override
	public void setLearningParams(Number... arguments) {
		learningFactor = (Double) arguments[0];
		basicLearningFactor = (Double) arguments[0];
		conscienceFactor = (Double) arguments[2];
		neighborhood = (Integer) arguments[3];
		neighborhoodSize = (Integer) arguments[4];
	}

	// public static void main(String[] args) throws Exception {
	//
	// Net net = Net.getInstance();
	// InputNeuron inputNeuron = new InputNeuron(Layer.INPUT_LAYER);
	// OutputNeuron outputNeuron = new OutputNeuron(Layer.OUTPUT_LAYER);
	// net.initWithOneInputAndOneOutput(inputNeuron, outputNeuron);
	// int neighborhood = KohonenLayer.neighborhood_1D;
	// double learningRatio = 0.9;
	// int learningSteps = 1;
	// double conscienceFactor = 0;
	// Neuron neuron = net.addKohonenLayer(neighborhood, learningRatio,
	// learningSteps,conscienceFactor);
	// neuron.setActivationFunction(ActivationFunc.nameToFunction.get("return"));
	// for (Connection c : neuron.getInputConnections()) {
	// c.setWeight(0.3);
	// }
	// for (Connection c : neuron.getOutputConnections()) {
	// c.setWeight(1);
	// }
	// net.setActivationFunctionForLayer(Layer.OUTPUT_LAYER,
	// ActivationFunc.nameToFunction.get("return"));
	// KohonenLayer kl = (KohonenLayer) net.layersMap.get(1);
	// net.fillInputNeuronWithSignal(0, 1);
	// for (Double d : net.stimulate()) {
	// System.out.println(d);
	// }
	//
	// kl.learn();
	// net.resetValues();
	// for (Double d : net.stimulate()) {
	// System.out.println(d);
	// }
	// }
}
